#include "kernel.h"

#pragma pack(1)

// Generic Address Structure (GAS)

#define ACPI_GAS_MEMORY 0
#define ACPI_GAS_IO 1
#define ACPI_GAS_PCI 2

#define ACPI_GAS_ACCESS_SIZE_UNDEFINED 0
#define ACPI_GAS_ACCESS_SIZE_BYTE 1
#define ACPI_GAS_ACCESS_SIZE_WORD 2
#define ACPI_GAS_ACCESS_SIZE_DWORD 3
#define ACPI_GAS_ACCESS_SIZE_QWORD 4

typedef struct _ACPI_GAS
{
  UINT8 AddressSpaceId;
  UINT8 RegisterBitWidth;
  UINT8 RegisterBitOffset;
  UINT8 AccessSize;
  UINT64 Address;
} ACPI_GAS, *PACPI_GAS;

C_ASSERT(sizeof(ACPI_GAS) == 12);

// Root System Description Pointer (RSDP)

typedef struct _ACPI_RSDP
{
  CHAR Signature[8];
  UINT8 Checksum;
  CHAR OemId[6];
  UINT8 Revision;
  UINT32 RsdtAddress;
  UINT32 Length;
  UINT64 XsdtAddress;
  UINT8 ExtendedChecksum;
  UINT8 Reserved[3];
} ACPI_RSDP, *PACPI_RSDP;

C_ASSERT(sizeof(ACPI_RSDP) == 36);

//
// Root System Description Table (RSDT)
//

typedef struct _ACPI_RSDT
{
  UINT8 Signature[4];
  UINT32 Length;
  UINT8 Revision;
  UINT8 Checksum;
  UINT8 OemId[6];
  UINT8 OemTableId[8];
  UINT32 OemRevision;
  UINT32 CreatorId;
  UINT32 CreatorRevision;
  UINT32 Entry[];
} ACPI_RSDT, *PACPI_RSDT;

C_ASSERT(sizeof(ACPI_RSDT) == 36);

// Multiple APIC Description Table (MADT)

typedef struct _ACPI_MADT
{
  UINT8 Signature[4];
  UINT32 Length;
  UINT8 Revision;
  UINT8 Checksum;
  UINT8 OemId[6];
  UINT8 OemTableId[8];
  UINT32 OemRevision;
  UINT32 CreatorId;
  UINT32 CreatorRevision;
  UINT32 LocalApicAddress;
  UINT32 Flags;
  UINT8 ApicStructures[];
} ACPI_MADT, *PACPI_MADT;

C_ASSERT(sizeof(ACPI_MADT) == 44);

#define ACPI_APIC_TYPE_PROCESSOR_LOCAL 0

typedef struct _ACPI_MADT_ENTRY 
{
  UINT8 Type;
  UINT8 Length;
} ACPI_MADT_ENTRY, *PACPI_MADT_ENTRY;

typedef struct _ACPI_PROCESSOR_LOCAL_APIC 
{
  UINT8 Type;
  UINT8 Length;
  UINT8 AcpiProcessorId;
  UINT8 ApicId;
  union {
    struct {
      UINT32 Enabled:1;
      UINT32 Reserved:31;
    } s1;
    UINT32 Flags;
  } u1;
} ACPI_PROCESSOR_LOCAL_APIC, *PACPI_PROCESSOR_LOCAL_APIC;

C_ASSERT(sizeof(ACPI_PROCESSOR_LOCAL_APIC) == 8);

//
//System Resource Affinity Table (SRAT)
//

typedef struct _ACPI_SRAT {
  UINT8 Signature[4];
  UINT32 Length;
  UINT8 Revision;
  UINT8 Checksum;
  UINT8 OemId[6];
  UINT8 OemTableId[8];
  UINT32 OemRevision;
  UINT32 CreatorId;
  UINT32 CreatorRevision;
  UINT8 Reserved[12];
  UINT8 SratStructures[];
} ACPI_SRAT, *PACPI_SRAT;

C_ASSERT(sizeof(ACPI_SRAT) == 48);

#define ACPI_SRAT_TYPE_PROC_AFFINITY_ENTRY 0
#define ACPI_SRAT_TYPE_MEM_AFFINITY_ENTRY 1

typedef ACPI_MADT_ENTRY ACPI_SRAT_ENTRY;
typedef PACPI_MADT_ENTRY PACPI_SRAT_ENTRY;

typedef struct _ACPI_SRAT_PROC_AFFINITY_ENTRY 
{
  UINT8 Type;
  UINT8 Length;
  UINT8 ProximityDomainLowEightBits;
  UINT8 ApicID;
  UINT32 Flags;
  UINT8 LocalApicEid;
  UINT8 ProximityDomainHighTwentyFourBits[3];
  UINT32 Reserved;
} ACPI_SRAT_PROC_AFFINITY_ENTRY, *PACPI_SRAT_PROC_AFFINITY_ENTRY;

typedef struct _ACPI_SRAT_MEM_AFFINITY_ENTRY 
{
  UINT8 Type;
  UINT8 Length;
  UINT32 ProximityDomain;
  UINT8 Reserved[2];
  UINT32 BaseAddressLow;
  UINT32 BaseAddressHigh;
  UINT32 LengthLow;
  UINT32 LengthHigh;
  UINT32 Reserved2;
  UINT32 Flags;
  UINT8 Reserved3[8];
} ACPI_SRAT_MEM_AFFINITY_ENTRY, *PACPI_SRAT_MEM_AFFINITY_ENTRY;

//
// Fixed ACPI Description Table (FADT)
//

#define ACPI_FADT_FLAGS_RESET_SUPPORTED 0x400

typedef struct _ACPI_FADT 
{
  UINT8 Signature[4];
  UINT32 Length;
  UINT8 Revision;
  UINT8 Checksum;
  UINT8 OemId[6];
  UINT8 OemTableId[8];
  UINT32 OemRevision;
  UINT32 CreatorId;
  UINT32 CreatorRevision;
  UINT8 Data1[112-36];
  UINT32 Flags;
  ACPI_GAS ResetRegister;
  UINT8 ResetValue;
  UINT8 Data2[244-129];
} ACPI_FADT, *PACPI_FADT;

C_ASSERT(FIELD_OFFSET(ACPI_FADT, Flags) == 112);
C_ASSERT(FIELD_OFFSET(ACPI_FADT, ResetRegister) == 116);
C_ASSERT(FIELD_OFFSET(ACPI_FADT, ResetValue) == 128);
C_ASSERT(sizeof(ACPI_FADT) == 244);

#pragma pack()

PACPI_FADT BlAcpiFadt;
PACPI_MADT BlAcpiMadt;
UINT32 BlAcpiNumberOfProcessors;
PACPI_RSDP BlAcpiRsdp;
PVOID BlAcpiRsdpAddress;
PACPI_RSDT BlAcpiRsdt;
PACPI_SRAT BlAcpiSrat;

PACPI_RSDP BlAcpiLocateRsdp(VOID)
{
  ULONG_PTR End;
  PACPI_RSDP Rsdp;
  ULONG_PTR Start;

  Start = 0xE0000;
  End = 0x100000;

  while (Start < End)
  {
    Rsdp = (PACPI_RSDP) Start;

    if ((Rsdp->Signature[0] == 'R') &&
      (Rsdp->Signature[1] == 'S') &&
      (Rsdp->Signature[2] == 'D') &&
      (Rsdp->Signature[3] == ' ') &&
      (Rsdp->Signature[4] == 'P') &&
      (Rsdp->Signature[5] == 'T') &&
      (Rsdp->Signature[6] == 'R') &&
      (Rsdp->Signature[7] == ' ') &&
      (BlRtlComputeChecksum8(Rsdp, 20) == 0)
      ) {

        return Rsdp;
    }

    Start += 0x10;
  }

  Start = (ULONG_PTR) BlMmExtendedBiosDataArea;
  End = Start + 0x10000;

  while (Start < End)
  {
    Rsdp = (PACPI_RSDP) Start;

    if ((Rsdp->Signature[0] == 'R') &&
      (Rsdp->Signature[1] == 'S') &&
      (Rsdp->Signature[2] == 'D') &&
      (Rsdp->Signature[3] == ' ') &&
      (Rsdp->Signature[4] == 'P') &&
      (Rsdp->Signature[5] == 'T') &&
      (Rsdp->Signature[6] == 'R') &&
      (Rsdp->Signature[7] == ' ') &&
      (BlRtlComputeChecksum8(Rsdp, 20) == 0)
      ) {

        return Rsdp;
    }

    Start += 0x10;
  }

  return NULL;
}

PACPI_RSDT BlAcpiLocateRsdt(PACPI_RSDP Rsdp)
{
  PACPI_RSDT Rsdt;

  Rsdt = (PACPI_RSDT) (ULONG_PTR) Rsdp->RsdtAddress;

  if (Rsdt == NULL)
    return NULL;

  if ((Rsdt->Signature[0] == 'R') &&
    (Rsdt->Signature[1] == 'S') &&
    (Rsdt->Signature[2] == 'D') &&
    (Rsdt->Signature[3] == 'T') &&
    (Rsdt->Length >= sizeof(ACPI_RSDT)) &&
    (BlRtlComputeChecksum8(Rsdt, Rsdt->Length) == 0)) {

      return Rsdt;
  }

  return NULL;
}

PACPI_MADT BlAcpiLocateMadt(PACPI_RSDT Rsdt)
{
  UINT32 Index;
  PACPI_MADT Madt;
  UINT32 NumberOfTables;

  NumberOfTables = (Rsdt->Length - FIELD_OFFSET(ACPI_RSDT, Entry)) / sizeof(Rsdt->Entry[0]);

  for (Index = 0; Index < NumberOfTables; Index += 1) {

    Madt = (PACPI_MADT) (ULONG_PTR) Rsdt->Entry[Index];

    if ((Madt->Signature[0] == 'A') &&
      (Madt->Signature[1] == 'P') &&
      (Madt->Signature[2] == 'I') &&
      (Madt->Signature[3] == 'C') &&
      (Madt->Length >= sizeof(ACPI_MADT)) &&
      (BlRtlComputeChecksum8(Madt, Madt->Length) == 0)) {

        return Madt;
    }
  }

  return NULL;
}

PACPI_SRAT BlAcpiLocateSrat(PACPI_RSDT Rsdt)
{
  UINT32 Index;
  PACPI_SRAT Srat;
  UINT32 NumberOfTables;

  NumberOfTables = (Rsdt->Length - FIELD_OFFSET(ACPI_RSDT, Entry)) / sizeof(Rsdt->Entry[0]);

  for (Index = 0; Index < NumberOfTables; Index += 1) 
{
    Srat = (PACPI_SRAT) (ULONG_PTR) Rsdt->Entry[Index];

    if ((Srat->Signature[0] == 'S') &&
      (Srat->Signature[1] == 'R') &&
      (Srat->Signature[2] == 'A') &&
      (Srat->Signature[3] == 'T') &&
      (Srat->Length >= sizeof(ACPI_SRAT)) &&
      (BlRtlComputeChecksum8(Srat, Srat->Length) == 0)) 
{
#if ACPI_VERBOSE
        BlRtlPrintf("ACPI: Found SRAT Table\n");
#endif

        return Srat;
    }
  }

  return NULL;
}

VOID BlAcpiDumpSratEntries(VOID)
{
  PACPI_SRAT_ENTRY Entry;
  PCHAR Limit;
  PACPI_SRAT_PROC_AFFINITY_ENTRY ProcAffinityEntry;
  PACPI_SRAT_MEM_AFFINITY_ENTRY MemAffinityEntry;
  PCHAR Next;
  UINT32 hbits;
  UINT32 totalbits;

  if (BlAcpiSrat == NULL) {
    BlRtlPrintf("ACPI: No Srat??\n");
    return;
  }
#if ACPI_VERBOSE
  BlRtlPrintf("SRAT:\n");
#endif

  Next = (PCHAR) &BlAcpiSrat->SratStructures[0];
  Limit = ((PCHAR) BlAcpiSrat) + BlAcpiSrat->Length;

  while (Next < Limit) 
{
    Entry = (PACPI_SRAT_ENTRY) Next;

    if ((Entry->Type == ACPI_SRAT_TYPE_PROC_AFFINITY_ENTRY) &&
      (Entry->Length >= sizeof(ACPI_SRAT_PROC_AFFINITY_ENTRY))) {

        ProcAffinityEntry = (PACPI_SRAT_PROC_AFFINITY_ENTRY) Next;
#if ACPI_VERBOSE
        BlRtlPrintf(" Processor:\n");
#endif
        hbits = 0;
        hbits += ProcAffinityEntry->ProximityDomainHighTwentyFourBits[0] << 24;
        hbits += ProcAffinityEntry->ProximityDomainHighTwentyFourBits[1] << 16;
        hbits += ProcAffinityEntry->ProximityDomainHighTwentyFourBits[2] << 8;
        totalbits = hbits | ProcAffinityEntry->ProximityDomainLowEightBits;
#if ACPI_VERBOSE
        BlRtlPrintf(" HighDomain 0x%06x LowDomain 0x%02x totalbits 0x%08x\n",
          hbits, ProcAffinityEntry->ProximityDomainLowEightBits,
          totalbits);
        BlRtlPrintf(" ApicID: %d flags 0x%08x\n", ProcAffinityEntry->ApicID,
          ProcAffinityEntry->Flags);
#endif
    }
    else if ((Entry->Type == ACPI_SRAT_TYPE_MEM_AFFINITY_ENTRY) &&
      (Entry->Length >= sizeof(ACPI_SRAT_MEM_AFFINITY_ENTRY))) {

        MemAffinityEntry = (PACPI_SRAT_MEM_AFFINITY_ENTRY) Next;
#if ACPI_VERBOSE
        BlRtlPrintf(" Memory:\n");
        BlRtlPrintf(" BaseAddress 0x%08x.%08x .. 0x%08x.%08x",
          MemAffinityEntry->BaseAddressHigh, MemAffinityEntry->BaseAddressLow,
          MemAffinityEntry->LengthHigh, MemAffinityEntry->LengthLow);
        BlRtlPrintf(" Domain %d Flags 0x%08x\n",
          MemAffinityEntry->ProximityDomain,
          MemAffinityEntry->Flags);
#endif
    }

    Next += Entry->Length;
  }
}

UINT32 BlAcpiGetNumberOfProcessors(VOID)
  //++
  //
  // Routine Description:
  //
  // This function returns the number of processors.
  //
  // Return Value:
  //
  // Number of processors.
  //
  //--

{
  PACPI_MADT_ENTRY Entry;
  PCHAR Limit;
  PACPI_PROCESSOR_LOCAL_APIC LocalApic;
  PCHAR Next;
  UINT32 NumberOfProcessors;

  if (BlAcpiMadt == NULL)
    return 1;

  Next = (PCHAR) &BlAcpiMadt->ApicStructures[0];
  Limit = ((PCHAR) BlAcpiMadt) + BlAcpiMadt->Length;
  NumberOfProcessors = 0;

  while (Next < Limit) 
  {
    Entry = (PACPI_MADT_ENTRY) Next;

    if ((Entry->Type == ACPI_APIC_TYPE_PROCESSOR_LOCAL) &&
      (Entry->Length >= sizeof(ACPI_PROCESSOR_LOCAL_APIC))) 
    {
        LocalApic = (PACPI_PROCESSOR_LOCAL_APIC) Next;

        if (LocalApic->u1.s1.Enabled != FALSE) 
 {
#if ACPI_VERBOSE

          BlRtlPrintf("ACPI: AcpiProcessorId=%u , LocalApicId=%u\n",
            LocalApic->AcpiProcessorId,
            LocalApic->ApicId);

#endif

          NumberOfProcessors += 1;
        }
    }

    Next += Entry->Length;
  }

  return NumberOfProcessors;
}

PACPI_FADT BlAcpiLocateFadt(PACPI_RSDT Rsdt)
{
  PACPI_FADT Fadt;
  UINT32 Index;
  UINT32 NumberOfTables;

  NumberOfTables = (Rsdt->Length - FIELD_OFFSET(ACPI_RSDT, Entry)) / sizeof(Rsdt->Entry[0]);

  for (Index = 0; Index < NumberOfTables; Index += 1) 
  {
    Fadt = (PACPI_FADT) (ULONG_PTR) Rsdt->Entry[Index];

    if ((Fadt->Signature[0] == 'F') &&
      (Fadt->Signature[1] == 'A') &&
      (Fadt->Signature[2] == 'C') &&
      (Fadt->Signature[3] == 'P') &&
      (BlRtlComputeChecksum8(Fadt, Fadt->Length) == 0)) {

        return Fadt;
    }
  }

  return NULL;
}

VOID BlAcpiResetSystem(VOID)
{
  if ((BlAcpiFadt->Revision < 2) ||
    (BlAcpiFadt->Length < (FIELD_OFFSET(ACPI_FADT, ResetValue) + sizeof(UINT8))) ||
    ((BlAcpiFadt->Flags & ACPI_FADT_FLAGS_RESET_SUPPORTED) == 0)) 
  {

#if ACPI_VERBOSE

    BlRtlPrintf("ACPI: Reset register is not supported! [FADT v%u]\n", BlAcpiFadt->Revision);

#endif

    return;
  }

#if ACPI_VERBOSE

  BlRtlPrintf("ACPI: Reset register type is %u.\n", BlAcpiFadt->ResetRegister.AddressSpaceId);

#endif

  switch (BlAcpiFadt->ResetRegister.AddressSpaceId) 
  {
  case ACPI_GAS_IO:
    __outbyte((UINT16) BlAcpiFadt->ResetRegister.Address, BlAcpiFadt->ResetValue);
    break;
  }
}

VOID BlAcpiInitialize(VOID)
{
  BlAcpiRsdp = BlAcpiLocateRsdp();

  if (BlAcpiRsdp == NULL) 
  {
    BlRtlPrintf("ACPI: No RSDP!\n");
    SysHalt();
  }

  BlAcpiRsdpAddress = (PVOID) BlAcpiRsdp;

  BlAcpiRsdt = BlAcpiLocateRsdt(BlAcpiRsdp);

  if (BlAcpiRsdt == NULL) 
  {
    BlRtlPrintf("ACPI: No RSDT!\n");
    SysHalt();
  }

  BlAcpiFadt = BlAcpiLocateFadt(BlAcpiRsdt);

  if (BlAcpiFadt == NULL) 
  {
    BlRtlPrintf("ACPI: No FADT!\n");
    SysHalt();
  }

  BlAcpiMadt = BlAcpiLocateMadt(BlAcpiRsdt);

  if (BlAcpiMadt == NULL)
    BlAcpiNumberOfProcessors = 1;
  else
    BlAcpiNumberOfProcessors = BlAcpiGetNumberOfProcessors();

  if (BlAcpiNumberOfProcessors == 0) 
  {
    BlRtlPrintf("ACPI: No local APIC!\n");
    SysHalt();
  }

  BlAcpiSrat = BlAcpiLocateSrat(BlAcpiRsdt);
  if (BlAcpiSrat != NULL)
    BlAcpiDumpSratEntries();


#if ACPI_VERBOSE

  BlRtlPrintf("ACPI: RSDP @ %p\nACPI: RSDT @ %p\nACPI: FADT @ %p [Revision=%u , Length=%u]\nACPI: MADT @ %p\nACPI: %u processor(s)\n",
    BlAcpiRsdp, BlAcpiRsdt, BlAcpiFadt, BlAcpiFadt->Revision, BlAcpiFadt->Length, BlAcpiMadt, BlAcpiNumberOfProcessors);

#endif

  //
  // Map APIC page uncached.
  //

  if ((BlAcpiMadt != NULL) && (BlAcpiMadt->LocalApicAddress != 0))
  {

#if ACPI_VERBOSE

    BlRtlPrintf("ACPI: APIC mapped @ %p.\n", BlAcpiMadt->LocalApicAddress);

#endif

    BlMmMapVirtualRange((PVOID) (ULONG_PTR) BlAcpiMadt->LocalApicAddress, (PVOID) (ULONG_PTR) BlAcpiMadt->LocalApicAddress, PAGE_SIZE, TRUE, FALSE, FALSE);
  }
}
